/// by Alvaro Cuno, Fev2008

#ifndef __RBF_EVAL_KERNEL_3TO1_H_
#define __RBF_EVAL_KERNEL_3TO1_H_

#include "rbfFunction.h"
#include "glslKernel.h"
#include "fboT.h"
#include <vector>
#include <cmath>

class RbfEvalKernel3to1 : public RbfFunction {
   
public:
   /// Default constructor
   RbfEvalKernel3to1():
      RbfFunction() {
      
      npolyterms = 0;
      pointsTexSize = 0;
      isDataInTexture0 = true;
      evalKernel.fragment_source("rbfEval_3to1.fsc");
      fbo.setNumberOfChannels(1);
   }
   
   ~RbfEvalKernel3to1() {
      glDeleteTextures(1, &texPoints);
   }
   
   /// Generates the fragments for the shading process
   void execute(vector<Number> &values) {
      
      fbo.on();
      isDataInTexture0 = true;
      evalKernel.use(true);
      
      /// Transfers the points to the GPU memory
      glActiveTexture(GL_TEXTURE2);
      glBindTexture(GL_TEXTURE_RECTANGLE_NV, texPoints);
      evalKernel.set_uniform("texturePoints", 2);
      
      glActiveTexture(GL_TEXTURE0);
      for (uint i=0; i<centers.size(); ++i) {
         glEnable(GL_TEXTURE_RECTANGLE_NV);
         
         if (isDataInTexture0) {
            glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); // writting into texture 0
            glBindTexture(GL_TEXTURE_RECTANGLE_NV, fbo.getTextureId(1)); // reading of texture 1
         }
         else {
            glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
            glBindTexture(GL_TEXTURE_RECTANGLE_NV, fbo.getTextureId(0));
         }
         
         evalKernel.set_uniform("center", centers[i][0], centers[i][1], centers[i][2], weights[i]);
         evalKernel.set_uniform("texturePhi", 0);
         isDataInTexture0 = !isDataInTexture0;
         
         glBegin(GL_QUADS);
           glVertex2f(0, 0);
           glVertex2f(pointsTexSize, 0);
           glVertex2f(pointsTexSize, pointsTexSize);
           glVertex2f(0, pointsTexSize);
         glEnd();
         
         glDisable(GL_TEXTURE_RECTANGLE_NV); 
      }
      glFinish();
      
      isDataInTexture0 = !isDataInTexture0;
      evalKernel.use(false);
      
      downloadData((Number*)&values[0]);
      fbo.off();
      
      /// add the polynomial part
   }
   
   ///
   void downloadData(float *data) {
      
      if (isDataInTexture0)      { glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); }
      else                       { glReadBuffer(GL_COLOR_ATTACHMENT1_EXT); }
      glReadPixels(0, 0, pointsTexSize, pointsTexSize, GL_RED, GL_FLOAT, data);
      glFinish();
   }
   
   void setCentersAndWeights(vector<Point3> &c, vector<Number> &w) {
      centers = c;
      weights = w;
   }
   
   ///
   void setEvalPoints(vector<Point3> &points) {
      
      pPoints = &points;
      int n = points.size();
      pointsTexSize = (int)(ceil(sqrt(n)));
      
      /// Makes a texture
      glGenTextures(1, &texPoints);
      
      /// Allocates texture memory that stores the RBF centers
      glBindTexture(GL_TEXTURE_RECTANGLE_NV, texPoints);
      glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_FLOAT_RGB32_NV, pointsTexSize, pointsTexSize, 
                   0, GL_RGB, GL_FLOAT, &points[0]);
      
      checkGLErrors("error: uploading the points to be evaluated");
      
      // FBO initialization
      fbo.initFBO(pointsTexSize, pointsTexSize);
      
      evalKernel.install(true);
   }
   
   void addPolynomialTerm(vector<Number> &pEval) {
      
      const int &n = centers.size();
      for (uint ii=0; ii<pPoints->size(); ++ii) {
         const Point3 &p = (*pPoints)[ii];
         Number r=0;
         for (unsigned int i=0; i<npolyterms; ++i) {
            const Number &valpoly = polynomial(p, i);
            r += weights[n+i]*valpoly;
         }
         pEval[ii] += r;
      }
   }
   
private:
   vector<Number>    weights;         // the vector of weights
   
protected:
   glslKernel        evalKernel;     // 
   FBO<2>            fbo;            // the frame buffer object 
   
   int               pointsTexSize;    // centers texture size
   GLuint            texPoints;        // Texture's ID of centers
   bool              isDataInTexture0; // defines where the result is on
};

#endif
